﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;

namespace Calculator.Models
{
    public class Expression
    {
        public static Expression Empty = new Expression();

        public bool IsUnary { get; private set; }
        public bool IsConstant { get { return Op == ExpressionOp.Constant || Op == ExpressionOp.Pi || Op == ExpressionOp.E; } }

        public ExpressionOp Op { get; set; }
        public Expression Left { get; set; }
        public Expression Right { get; set; }
        public double Value { get; private set; }

        public bool IsEmpty { get { return Op == ExpressionOp.None; } }

        public Expression()
            : base()
        {
            Op = ExpressionOp.None;
            IsUnary = true;
        }

        public Expression(ExpressionOp op, Expression left, Expression right)
            : base()
        {
            this.Op = op;
            this.Left = left;
            this.Right = right;
            IsUnary = false;
        }

        public Expression(ExpressionOp op, Expression expression)
            : this(op, null, expression)
        {
            IsUnary = true;
        }

        public Expression(double value)
            : base()
        {
            this.Value = value;
            Op = ExpressionOp.Constant;
        }

        /// <summary>
        /// Calculates the result.
        /// </summary>
        /// <param name="useOrderOfOperations">Set to true to use order of operations (BODMAS) to calculate the expression which prefers brackets over exponents over multiplications over additions, otherwise false to calculate in the given order.</param>
        /// <returns>The result of the expression, otherwise double.MaxValue if an error occured.</returns>
        public double Calculate(bool useOrderOfOperations)
        {
            try
            {
                if (useOrderOfOperations)
                {
                    Expression e = ExpressionUtil.OrderOfOperations(this);
                    return e.CalculateRecursive();
                }
                else
                {
                    return CalculateRecursive();
                }
            }
            catch
            {
                return double.MaxValue;
            }
        }

        private double CalculateRecursive()
        {
            switch (Op)
            {
                case ExpressionOp.Pi: return Math.PI;
                case ExpressionOp.Constant: return Value;
            }
            try
            {
                if (IsUnary)
                {
                    double value = Right != null ? Right.CalculateRecursive() : 0d;
                    return CalculateUnary(value);
                }
                else
                {
                    double v1 = Left.CalculateRecursive();
                    double v2 = Right != null ? Right.CalculateRecursive() : 0d;
                    return CalculateBinary(v1, v2);
                }
            }
            catch
            {
                return double.MaxValue;
            }
        }

        private double CalculateBinary(double v1, double v2)
        {
            switch (Op)
            {
                case ExpressionOp.Add: return v1 + v2;
                case ExpressionOp.Substract: return v1 - v2;
                case ExpressionOp.Multiply: return v1 * v2;
                case ExpressionOp.Divide: return v1 / v2;
                case ExpressionOp.Pow: return Math.Pow(v1, v2);
                default: return v1;
            }
        }

        private double CalculateUnary(double value)
        {
            switch (Op)
            {
                case ExpressionOp.None: return value;
                case ExpressionOp.Faculty: return Faculty(value);
                
                case ExpressionOp.Sin: return Math.Sin(value);
                case ExpressionOp.Cos: return Math.Cos(value);
                case ExpressionOp.Tan: return Math.Tan(value);

                case ExpressionOp.ASin: return Math.Asin(value);
                case ExpressionOp.ACos: return Math.Acos(value);
                case ExpressionOp.ATan: return Math.Atan(value);
                
                case ExpressionOp.SinH: return Math.Sinh(value);
                case ExpressionOp.CosH: return Math.Cosh(value);
                case ExpressionOp.TanH: return Math.Tanh(value);
                case ExpressionOp.ASinH: return MathUtil.ASinH(value);
                case ExpressionOp.ACosH: return MathUtil.ACoseH(value);
                case ExpressionOp.ATanH: return MathUtil.ATanH(value);

                
                case ExpressionOp.Sqrt: return Math.Sqrt(value);
                case ExpressionOp.Square: return Math.Pow(value, 2d);
                case ExpressionOp.Cube: return Math.Pow(value, 3d);
                case ExpressionOp.Ln: return Math.Log(value);
                case ExpressionOp.Log: return Math.Log10(value);
                case ExpressionOp.Reciproc: return 1d / value;
                case ExpressionOp.OpenBracket:
                case ExpressionOp.Bracket: return value;
                case ExpressionOp.Exp: return Math.Exp(value);
                default: return 0d;
            }
        }

        private double Faculty(double value)
        {
            double result = 1d;
            int n = (int)value;
            for (int v = 2; v < n; v++)
            {
                result *= v;
            }
            return result;
        }

        public override string ToString()
        {
            try
            {
                switch (Op)
                {
                    case ExpressionOp.None: return Right != null ? Right.ToString() : "0";
                    case ExpressionOp.Pi: return "π";
                    case ExpressionOp.Constant: return Value.ToString();
                    case ExpressionOp.Add: return BinString("+");
                    case ExpressionOp.Substract: return BinString("-");
                    case ExpressionOp.Multiply: return BinString("*");
                    case ExpressionOp.Divide: return BinString("/");
                    case ExpressionOp.Pow: return BinString("^");
                    case ExpressionOp.Reciproc: return "1/" + Right.ToString();
                    case ExpressionOp.Square: return "(" + Right.ToString() + ")²";
                    case ExpressionOp.Cube: return "(" + Right.ToString() + ")³";
                    case ExpressionOp.Sqrt: return Unstring("√");

                    case ExpressionOp.Sin: return Unstring("sin");
                    case ExpressionOp.Cos: return Unstring("cos");
                    case ExpressionOp.Tan: return Unstring("tan");

                    case ExpressionOp.SinH: return Unstring("sinh");
                    case ExpressionOp.CosH: return Unstring("cosh");
                    case ExpressionOp.TanH: return Unstring("tanh");

                    case ExpressionOp.ASin: return Unstring("asin");
                    case ExpressionOp.ACos: return Unstring("acos");
                    case ExpressionOp.ATan: return Unstring("atan");

                    case ExpressionOp.ASinH: return Unstring("asinh");
                    case ExpressionOp.ACosH: return Unstring("acosh");
                    case ExpressionOp.ATanH: return Unstring("atanh");


                    case ExpressionOp.Ln: return Unstring("ln");
                    case ExpressionOp.Log: return Unstring("log");
                    case ExpressionOp.Exp: return Unstring("exp");
                    case ExpressionOp.Faculty: return "(" + Right.ToString() + ")!";
                    case ExpressionOp.OpenBracket: return BracketString();
                    case ExpressionOp.Bracket: return Unstring("");
                    default: return "not defined";

                }
            }
            catch (Exception)
            {
                return "ERROR";
            }
        }

        private string BracketString()
        {
            string right = Right != null ? Right.ToString() : "";
            return "(" + right;
        }

        private string Unstring(string op)
        {
            if (Right != null)
            {
                return op + "(" + Right.ToString() + ")";
            }
            else return "";
        }

        private string BinString(string conc)
        {
            StringBuilder sb = new StringBuilder();
            if (Left != null)
            {
                sb.Append(Left.ToString());
            }
            sb.Append(" ");
            sb.Append(conc);
            if (Right != null)
            {
                sb.Append(" ");
                sb.Append(Right.ToString());
            }
            return sb.ToString();
        }
    }

}
